"""
copyright (c) 2009, paketka@gmail.com et. al
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright notice,
      this list of conditions and the following disclaimer.

    * Redistributions in binary form must reproduce the above copyright notice,
      this list of conditions and the following disclaimer in the documentation
      and/or other materials provided with the distribution.

    * Neither the name of the <ORGANIZATION> nor the names of its contributors
      may be used to endorse or promote products derived from this software
      without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""
import os
import os.path
import re
import Image
import datetime
from django.template import Library
from django import template
from settings import THUMBDIR, MEDIA_ROOT, IMG_THUMBS, MEDIA_URL
from django.utils.safestring import mark_safe

register = Library()

def removeThumb(imgFileName):
    """
    deletes img thumb it must construct dirctory for given img thumb
    """
    imgDir, fileName = os.path.split(imgFileName)
    thumbDir = os.path.join(imgDir, THUMBDIR)

    if os.access(thumbDir, os.F_OK):
        thumbs = os.listdir(thumbDir)
        baseFile, extension = os.path.splitext(fileName)
        pattern = None

        if extension and len(extension):
            pattern = re.compile(baseFile + '.*\\' + extension)
        else:
            pattern = re.compile(baseFile + '.*')

        for thumbFile in thumbs:
            if pattern.match(thumbFile) != None:
                os.remove(os.path.join(thumbDir, thumbFile))

    return

def getWH(whArgStr):
    """
    function converts string to list of two ints
    """

    retVal = [int(x) for x in whArgStr.split('x')]
    retVal = retVal[:2]
    l = len(retVal)

    if l == 0:
        retVal = [200, 200]
    elif l == 1:
        retVal[1] = retVal[0]

    return retVal

@register.filter
def thumb(imgPath, whArgStr = '200x200'):
    """
    imgPath is string with path to original image file 
    xyArg is string in format 'WxH' (width x height), w, h are numbers

    the thumbnail will be stored into subdirectory called thumbs
    the thumbs resides in the same directory where orig img is present
    returns thumbnail of given image

    it creates subdirectory thumbs if it does not exist
    """

    if imgPath == None or len(imgPath) == 0:
        return ''

    dirName, fileName = os.path.split(imgPath)
    dir, imgDir = os.path.split(dirName)
    thumbDir = os.path.join(dirName, THUMBDIR)

    basePathName, ext = fileName.rsplit('.', 1)
    thumbFileName = basePathName + '_' + whArgStr + '.' + ext
    thumbFullPath = os.path.join(thumbDir, thumbFileName)
    thumbFullPath = os.path.join(MEDIA_ROOT, thumbFullPath)
    #strip media path to get relative path to thumb
    thumbURL = MEDIA_URL + '/' + imgDir + '/' + THUMBDIR
    thumbURL += '/' + thumbFileName
    thumbDir = os.path.join(MEDIA_ROOT, thumbDir)

    if os.access(thumbDir, os.F_OK) == False:
        os.mkdir(thumbDir)


    # TODO: better check should be applied here
    if not os.path.exists(thumbFullPath):
        origImgFullPath = os.path.join(MEDIA_ROOT, imgPath)
        origImg = Image.open(origImgFullPath)
        origImg.thumbnail(getWH(whArgStr))
        origImg.save(thumbFullPath)

    return thumbURL


@register.filter
def mf(val, mfAttr):
    """
    Mircoformat - wraps val into span with mfAttr class.
    Intended usage is as follows:
        {{ person.name|mf_span:"name" }}
    Suppose person.name variable is set to 'Charlie' the expression above will
    render following HTML:
        <span class="name">Charlie</span>
    """
    return mark_safe('<span class="%s">%s</span>' % (mfAttr, val))


@register.inclusion_tag('table_header.html')
def table_header(model):
    """
    Outputs table header from model class.
    it will do something like <span class="columnName"> xxxx </span>
    """
    tableHeaders = None

    if hasattr(model, '_meta') and hasattr(model._meta, 'admin') and hasattr(model._meta.admin, 'fields'):
        for element in model._meta.admin.fields:
            if type(element) == type({}) and element.has_key('fields'):
                tableHeaders = element['fields']
                break
    else:
        tableHeaders = []
        for field in model._meta.fields:
            if field.editable:
                tableHeaders.append(field.name)

    return {'columns'   : tableHeaders, }


@register.inclusion_tag('table_row.html')
def table_row(instance):
    """
    Outputs a single data row of model
    """
    values = []
    columns = table_header(instance)
    for column in columns['columns']:
        values.append(getattr(instance, column))

    return { 'rowFields' : values }



class DatepickerNode(template.Node):
    datepickerTemplate = """
<!-- HEAD_include_js jquery-ui-1.7.custom.min.js -->


<script type="text/javascript">
    var enabledDays_{{ datepickerId }} = {};

    $(function() {
        {% if date %}
        var date = new Date(
                        {{ date.year }},
                        {{ date.month|add:"-1" }},
                        {{ date.day }}
                    );
        {% else %}
        var date = new Date();
        {% endif %}

        $("#{{ datepickerId|safe }}").datepicker({
            altFormat: 'yy/mm/dd/',
            dateFormat: 'yy/mm/dd/',
            defaultDate: date,
            changeMonth: true,
            changeYear: true,
            onChangeMonthYear: function(year, month) {
                    finished_{{ datepickerId }} = false;
                    jsonUrl = "{{ urlCalDataJSON }}/"
                    jsonUrl += year + '/';
                    jsonUrl += month + '/';
                    $.ajax({
                        url : jsonUrl,
                        async : false,
                        dataType : 'json',
                        success : function(data) {
                            enabledDays_{{ datepickerId }} = data;
                        }
                    });
                },
            beforeShowDay: function(date) {
                    var retVal = [];

                    if (enabledDays_{{ datepickerId|safe }}[date.getDate()] != 0) {
                        retVal[0] = true;
                        retVal[1] = '';
                    }
                    else {
                        retVal[0] = false;
                        retVal[1] = '';
                    }
                    
                    return (retVal);
                },
            {% if onSelect %}
                onSelect: {{ onSelect }}
            {% else %}
                {% if jqGridId %}
                    onSelect: function(dateText) {
                        var oldUrl = $("#{{ jqGridId|safe }}").getGridParam("url");
                        var baseUrl = oldUrl.replace(/\d{4}\/\d{1,2}\/\d{1,2}\//g,'');
                        var newUrl = baseUrl + dateText;
                        $("#{{ jqGridId|safe }}").setGridParam({ caption: dateText });
                        $("#{{ jqGridId|safe }}").setGridParam({ url: newUrl }).trigger("reloadGrid");
                    }
                {% else %}
                    onSelect: function(dateText) {
                            var urlToLoad = '{{ urlSelectedDateBase|safe }}'
                            urlToLoad += '/' + dateText;
                            window.location.assign(urlToLoad);
                        }
                {% endif %}
            {% endif %}
            {% if dateStart and dateEnd %}
                ,
                minDate: new Date({{ dateStart.year }}, {{ dateStart.month }} - 1, {{ dateStart.day }}),
                maxDate: new Date({{ dateEnd.year }}, {{ dateEnd.month }} - 1, {{ dateEnd.day }})
            {% endif %}
            }
        );
    });
</script>

<div id="{{ datepickerId }}">
</div>
    """
    def __init__(self, divId, date, urlCalDays, urlDayData, jqGridId, dateStart, dateEnd, onSelect):
        """
        constructor takes these parameters:
            month, year - the month we want to generate event calendar
            qs - query set with events,
            dateAttName - date attribute in QS objects,
            urlTempl - i.e. /showEventForDate/%d/%d, which will
                be turned to /showEventForDate/04/2008
            jqGridId - id of HTML element, where jqgrid object is bound,
                (or consumer for onSelect event)
        """
        self.divId = divId
        self.date = date
        self.urlCalDays = urlCalDays
        self.urlDayData = urlDayData
        self.dateStart = dateStart
        self.dateEnd = dateEnd
        self.jqGridId = jqGridId
        self.onSelect = onSelect

    def render(self, context):
        calDaysPattern = re.compile(r'(?P<baseUrl>.+)(?P<scratchIt>/\d{4}/\d{1,2}/?)', re.U)
        dayDataPattern = re.compile(r'(?P<baseUrl>.+)(?P<scratchIt>/\d{4}/\d{1,2}/\d{1,2}/?)', re.U)

        urlCalDays = self.urlCalDays.resolve(context)
        urlDayData = self.urlDayData.resolve(context)

        match = calDaysPattern.match(urlCalDays)
        if match != None:
            urlCalDays = match.group('baseUrl')

        match = dayDataPattern.match(urlDayData)
        if match != None:
            urlDayData = match.group('baseUrl')

        tmpl = template.Template(self.datepickerTemplate)

        if self.jqGridId != None:
            self.jqGridId = self.jqGridId.resolve(context)

        if self.onSelect != None:
            self.onSelect = self.onSelect.resolve(context)

        if self.dateStart != None:
            self.dateStart = self.dateStart.resolve(context)

        if self.dateEnd != None:
            self.dateEnd = self.dateEnd.resolve(context)

        cx = template.Context({
                'datepickerId' : self.divId.resolve(context),
                'urlCalDataJSON' : urlCalDays,
                'urlSelectedDateBase' : urlDayData,
                'date' : self.date.resolve(context),
                'dateStart' : self.dateStart,
                'dateEnd' : self.dateEnd,
                'jqGridId' : self.jqGridId,
                'onSelect' : self.onSelect,
            })
        
        return tmpl.render(cx)


@register.tag
def datepicker(parser, token):
    """
    the urlCalDays will refere to some particular cal url i.e.
        json/jednaciDny/2008/03
    we need to turn this instance refering to given month, to generic part like
    json/jednaciDny/

    same applies to urlDayData argument.

    The template tag options are screwed up a bit right now.  In case three
    options are passed:
        {% datepicker "html id" calData urlDay %}
    the simple datepicker will be generated:
        "html id"   - is element ID, where datepicker will be bound

        calData     - is URL, where from 'busy days' will be fetched i.e.
                      json/jednaciDny/2008/03
        urlDay      - URL to details about particular busy day, once user will
                      click to particular busy day, default onSelect() method
                      will be invoked. Method will open a new tab with busy day
                      details
    the datepicker will be preset to current month.

    if you don't want to datepicker to be preset to current month, you can
    pass a month/year as a datepicker initial value, then you need to
    invoke datepicker as follows:
        {% datepicker "html id" calData urlDay date %}

    sometimes you don't want to use default action (to open tab with desired
    detail page). Another 'default' action is to pass a new AJAX url to jqGrid,
    to allow jqGrid to fetch data for another month, without opening a new tab.
    you need to pass an extra argument to datepicker then: element id of jqgrid
    to update its url. It will look as follows then:
        {% datepicker "html id" calData urlDay date "jqGridId" %}
    
    If you don't want to use any of predefined onSelect callback, you might
    want to supply your own. to achieve this a keyword argument must be used,
    where jqGridId is passed:
        {% datepicker "html id" calData urlDay date onSelect="myOnSelect" %}

    Remember it's the arguments must be passed in order, which was introduced here.
    The only keyword argument is onSelect. onSelect must be passed as last.

    Time to time we need to define a range - startDate, endDate - for a grid.
    In case we don't want to define callback, then following templatetag should
    be used:
        {% datepicker "html id" calData urlDay date start end %}

    if callback should be used, pass it again as a last argument:
        {% datepicker "html id" calData urlDay date start end onSelect="myOnSelect" %}
    """

    params = token.split_contents()
    
    if len(params) == 4:
        divId = params[1]
        urlCalDays = params[2]
        urlDayData = params[3]
        date = datetime.datetime.today()
        dateStart = None
        dateEnd = None
        jqGridId = None
        onSelect = None
    elif len(params) == 5:
        divId = params[1]
        urlCalDays = params[2]
        urlDayData = params[3]
        date = params[4]
        dateStart = None
        dateEnd = None
        jqGridId = None
        onSelect = None
    elif len(params) == 6:
        divId = params[1]
        urlCalDays = params[2]
        urlDayData = params[3]
        date = params[4]
        dateStart = None
        dateEnd = None

        onSelect = None
        jqGridId = None
        if '=' in params[5]:
            k, v = params[5].split('=', 1)
            if k != 'onSelect':
                errorStr = "Unexpected kw arg (%s)" % k
                raise template.TemplateSyntaxError, errorStr

            onSelect = parser.compile_filter(v)
        else:
            jqGridId = parser.compile_filter(params[6])
    elif len(params) == 7:
        divId = params[1]
        urlCalDays = params[2]
        urlDayData = params[3]
        date = params[4]
        dateStart = parser.compile_filter(params[5])
        dateEnd = parser.compile_filter(params[6])
        onSelect = None
        jqGridId = None
    elif len(params) == 8:
        divId = params[1]
        urlCalDays = params[2]
        urlDayData = params[3]
        date = params[4]
        dateStart = parser.compile_filter(params[5])
        dateEnd = parser.compile_filter(params[6])

        onSelect = None
        jqGridId = None
        if '=' in params[7]:
            k, v = params[7].split('=', 1)
            if k != 'onSelect':
                errorStr = "Unexpected kw arg (%s)" % k
                raise template.TemplateSyntaxError, errorStr

            onSelect = parser.compile_filter(v)
        else:
            jqGridId = parser.compile_filter(params[6])
    else:
        errorStr =  "%s requires 3, 5, 6, 7 or 8 arguments" % token.split_contents()[0] 
        raise template.TemplateSyntaxError, errorStr
    
    return DatepickerNode(
                    divId = parser.compile_filter(divId),
                    date = parser.compile_filter(date),
                    urlCalDays = parser.compile_filter(urlCalDays),
                    urlDayData = parser.compile_filter(urlDayData),
                    dateStart = dateStart,
                    dateEnd = dateEnd,
                    jqGridId = jqGridId,
                    onSelect = onSelect
                )

